# 实现多任务并发执行
# 在CPython的解释器实现中，存在GIL(Global Interpreter Lock)全局解释器锁，
# CPython的实现机制中确保同一时间只会有一个线程执行python字节码，该种实现确保了
# 对象模型(dict、list、tuple..)的并发访问安全性，同时也牺牲了多核的并行处理能力
# 对于此问题，可以使用标准库或者第三方库实现并发执行。
# python中可以通过多线程、多进程方式实现并发执行：
#      1. threading多线程模块属于python标准库参照了java线程模型，提供了多线程能力，是_thread模块的高级别封装。
#         但是由于GIL机制，同一时间只能运行一个线程，可以实现多线程交替执行(类似java vertx框架的worker模式)
#         该模块主要用于io密集型任务，可以高效利用cpu，对于计算密集型(压缩、hash计算等)并不适用
#      2. multiprocessing，多进程模式，充分利用多核最大化cpu使用率

# threading 模块使用
# 通过threading实现线程方式有两种
#   1. 通过threading.Thread(target=callable)传递可调用的对象，然后通过thread.start()启动
#   2. 继承threading.Thread类，重写__init__和run方法
import threading 
import time

# 自定义函数
def thread_local_data():
    print('hello -> %s %s' % (threading.get_ident(), threading.get_native_id())) # 获取线程标识
    # 线程本地数据存储对象，类似java threadlocal
    lc = threading.local()
    lc.a = 'hello local' # 设置任意值，如果调用其他函数，同一个线程可以获取到
    print('threadloal -> %s data -> %s' % (lc, lc.a)) 

def f1_thread_local():
    # 创建线程对象，将函数传递作为参数，线程类构造方法参数全是关键字参数
    my_thread = threading.Thread(target=thread_local_data,name='myThread')
    # 启动线程，线程将根据调度运行run方法，run方法中调用target函数
    my_thread.start()
    time.sleep(0.2)
    # 获取当前线程，即主线程
    print('main thread -> %s' % threading.current_thread())

    print('my_thread live -> %s' % my_thread.is_alive())


# 继承方式实现线程
class MyThread(threading.Thread):
    
    def __init__(self):
        # 如果重写构造方法，必须调用父类构造方法初始化
        super().__init__()

    def run(self):
        print(f'hello {threading.current_thread()}')

def f2_wirte_my_thread():
    t = MyThread()
    print(t)
    t.start()
    time.sleep(0.1)
    print('ok')

def f3_lock():
    # Lock 锁 
    lock = threading.Lock() # 创建锁
    print(lock)
    def f2():
        lock.acquire()
        try:
            print(f'{threading.current_thread().name} f2 print')
            time.sleep(0.6)
        finally:
            lock.release()
    def f3():
        # 等价于f2的锁获取和释放代码
        with lock:
            print(f'{threading.current_thread().name} f3 print')
            time.sleep(0.5)

    t1 = threading.Thread(target=f2)
    t2 = threading.Thread(target=f3)
    t2.start()
    t1.start()


def f4_rlock():
    # RLock reentrant lock 可重入锁
    rlock = threading.RLock()
    def f4():
        for i in range(3):
            rlock.acquire()
            try:
                print(f'{threading.current_thread().name} print', i)
                time.sleep(0.2)
            finally:
                rlock.release()
    def f5():
        for i in range(3):
            with rlock:
                print(f'{threading.current_thread().name} print', i)
                time.sleep(0.2)

    t1 = threading.Thread(target=f4, name='thread-f4')
    t2 = threading.Thread(target=f5, name='thread-f5')
    t2.start()
    t1.start()

def f5_condition():
    # Condition 条件 生产者-消费者
    condit = threading.Condition() # 有一个可选参数lock，可以传lock/Rlock，不传默认创建一个RLock
    eggs = []
    def consumer():
        while True:
            with condit: # condition也是acquire和release获取和释放，此处使用with表达式自动获取和释放
                if len(eggs) == 0 and not condit.wait(1): # wait返回true表示被唤醒，timeout返回false
                        break
                print(f'consume -> {eggs.pop()}')

    def producer():
        for i in range(5):
            with condit:
                print(f'produce egg {i}')
                eggs.append('egg' + str(i))
                condit.notify()
                time.sleep(0.3)
            
    t1 = threading.Thread(target=consumer)
    t2 = threading.Thread(target=producer)
    t2.start()
    t1.start()

def f6_semaphore():
    # Semaphore / BoundedSemaphore(限定固定个数) 信号量
    # sem = threading.BoundedSemaphore(1) 
    sem = threading.Semaphore(2) 
    def f6(lock):
        for i in range(3):
            with lock:
                time.sleep(0.3)
                print(f'{threading.current_thread().name} print {i}')
    # Thead构造函数能接受位置参数和关键字参数，这两个参数最终会再调用target是传递给target
    t1 = threading.Thread(target=f6, args=(sem,)) # 位置参数, 注意(xx,)元组仅有一个元素要加,否则会解析成单一变量
    t2 = threading.Thread(target=f6, args=(sem,))
    t2.start()
    t1.start()

def f76_event():
    # Event 事件对象控制多线程执行
    # 通过set()方法将标志设为true, clear()方法重置为false，wait()方法等待标志位true执行
    ev = threading.Event()
    def f7():
        ev.wait()
        print(f'{threading.current_thread().name} go')
    t1 = threading.Thread(target=f7)
    t2 = threading.Thread(target=f7)
    t2.start()
    t1.start()
    time.sleep(1)
    ev.set() # 标志置为true
    print(ev.is_set())


def f8_delay_timer():
    # Timer 延时执行器，在指定时间后执行某个方法
    # 1秒后打印
    t = threading.Timer(1, lambda: print(f'{threading.current_thread().name}'))
    t.start() # 启动定时器
    # t.cancel() # 取消延时定时器， 只有在定时器未执行时可以取消


def f9_barrier():
    # Barrier 相当于java的countDownLatch, 不过不需要countdown，只需要调用wait
    # 当wait执行n此后，全部自动释放，reset()重置
    bar = threading.Barrier(2) 
    def f8():
        bar.wait() # 调用两次后自动释放, 第一次调用的处于wait状态等待
        print(f'{threading.current_thread().name} run')
    t1 = threading.Thread(target=f8)
    t2 = threading.Thread(target=f8)
    t2.start()
    time.sleep(2)
    t1.start()

if __name__ == '__main__':
    # f1_thread_local()
    # f2_wirte_my_thread()
    # f3_lock()
    # f4_rlock()
    # f5_condition()
    # f6_semaphore()
    # f7_event()
    # f8_delay_timer()
    f9_barrier()